/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jigdo.downloader;

import jigdo.JigdoException;
import java.io.*;
import java.util.ArrayList;
import java.util.logging.Logger;
import jigdo.DiskIO.ProgressEntry;
import jigdo.data.jigdo.*;
import jigdo.data.template.*;
import jigdo.data.template.section.*;
import jigdo.data.template.section.DESCEntries.*;

/**
 * Represents a single ISO download and matches components from the jigdo
 * fileset together.
 *
 * @author John R Sohn
 * @see jigdo.data.jigdo.JigdoFile
 * @see jigdo.data.template.JigdoTemplate
 * @see jigdo.downloader.DownloadEntry
 */
public class ImageDownloadTask implements DownloadListener {

    Logger logger = Logger.getLogger(this.getClass().getName());
    
    private int nextdownloadindex = -1;

    public DownloadEntry NextDownload() {

        if (PendingDownloads.isEmpty()) {
            nextdownloadindex = 0;
            return null;
        }

        nextdownloadindex++;

        synchronized (PendingDownloads) {

            while (PendingDownloads.get(nextdownloadindex).
                    getDownloadStatus() != DownloadStatus.NotStarted) {
                if (nextdownloadindex == PendingDownloads.size()) {
                    nextdownloadindex = 0;
                } else {
                    nextdownloadindex++;
                }
            }

            return PendingDownloads.get(nextdownloadindex);
        }

    }
    
    jigdo.DiskIO.ISOWriter isowriter;
    
    public ImageStatus Status = ImageStatus.NotStarted;

    /**
     * The priority of this download, for rules see downloadpriority.
     *
     * @see jigdo.downloader.DownloadPriority
     */
    public DownloadPriority Priority;
    /**
     * Contains the base URL with trailing / indicating the location of the file
     * repo. To be updated.
     */
    public String BaseServerURL = "http://ftp.us.debian.org/debian/";
    /**
     * Contains the path pointing to the matched .jigdo file.
     */
    public String JigdoFilename;
    /**
     * The parsed jigdo file.
     */
    public JigdoFile jigdofile;
    /**
     * The parsed template file.
     */
    public JigdoTemplate jigdotemplate;
    /**
     * Contains all the file entry information for this iso.
     */
    private java.util.ArrayList<DownloadEntry> PendingDownloads;
    /**
     * Array List of finished downloads.
     */
    private java.util.ArrayList<DownloadEntry> FinishedDownloads;
    /**
     * Collection of currently active downloads.
     */
    private java.util.ArrayList<DownloadEntry> ActiveDownloads;

   
    
    /**
     * Kicks off the process of loading and matching entries from the .jigdo
     * file and .template file of the download task and organizes them into a
     * list of entries, ready to be downloaded and written to the ISO file.
     *
     * @param jigdoFilename Filename of .jigdo file containing information about
     * the files to be downloaded.
     * @throws IOException
     * @throws JigdoException
     */
    public ImageDownloadTask(String jigdoFilename) throws IOException, JigdoException {

        try {
            FinishedDownloads = new java.util.ArrayList<DownloadEntry>();
            PendingDownloads = new java.util.ArrayList<DownloadEntry>();
            listeners = new ArrayList<ImageDownloadListener>();

            JigdoFilename = jigdoFilename;
            File file = new File(jigdoFilename);

            String basepath = file.getParent();
            basepath = basepath == null ? "" : basepath + File.separatorChar;

            byte[] jigdobytes =
                    jigdo.utils.FileUtils.ReadAllBytes(jigdoFilename);

            jigdofile = jigdo.io.JigdoFileReader.ReadFile(jigdobytes);

            jigdobytes = null;

            byte[] templatebytes =
                    jigdo.utils.FileUtils.ReadAllBytes(basepath + jigdofile.Template);

            
            
            

            if (!jigdo.utils.MD5Helpers.GetJigdoMD5(templatebytes).equals(jigdofile.Template_MD5Sum)) {
                throw new jigdo.JigdoException("Template file does not match md5 sum");
            }

            jigdotemplate = jigdo.io.TemplateFileReader.ReadEntry(templatebytes);

           isowriter = new jigdo.DiskIO.ISOWriter(this.jigdofile.Filename, this);
            
            this.matchDownloadEntries();

            
        } catch (Exception e) {
            throw new jigdo.JigdoException("Error occurred while creating imagedownload task", e);
        }


   
        setStatus(ImageStatus.NotStarted);
        

    }
    
    
    private void matchDownloadEntries() throws JigdoException
    {
        
       ArrayList<ProgressEntry> searchentries=  (ArrayList<ProgressEntry>) 
               isowriter.Progress.Entries.clone();
       
        for (int jigdoIndex = 0; jigdoIndex < jigdofile.Files.size(); jigdoIndex++) 
        {

                JigdoRemoteFileEntry searchItem =
                        jigdofile.Files.get(jigdoIndex);

                TemplateDESCFileInfoEntry match = null;

                
                java.util.ArrayList<TemplateDESCSection.FileSearchEntry> entries = jigdotemplate.Description.GetSearch();


                for (int DESCIndex = 0; DESCIndex < entries.size(); DESCIndex++) {
                    TemplateDESCFileInfoEntry potentialmatch = entries.get(DESCIndex).Parent;

                    if (potentialmatch.FileMD5Base64.equals(searchItem.MD5)) {
                        match = potentialmatch;
                        entries.remove(DESCIndex);
                        break;
                    }

                }

                if (match == null) {

                    throw new JigdoException("Error in .jigdo file, no corresponding template entry found for file:" + searchItem.URL);
                } else {

                    DownloadEntry currFile = new DownloadEntry(this);

                    currFile.JigdoEntry = searchItem;
                    currFile.TemplateEntry = match;

                    currFile.AddListener(this);
                    
                    
                    ProgressEntry entry =null;
                    
                    for (int x=0; x < searchentries.size(); x++)
                    {
                        if ( searchentries.get(x).FileMD5.equals(currFile.JigdoEntry.MD5))
                        {
                            entry = searchentries.get(x);
                            break;
                        }
                    }
                    
                    if (entry==null)
                    {
                        
                        entry = new ProgressEntry();
                        entry.FileMD5 = currFile.JigdoEntry.MD5;
                        isowriter.Progress.Entries.add(entry);
                        
                    }
                    else   
                    {
                        searchentries.remove(entry);
                         
                    }                    

                    currFile.setProgressEntry(entry);
                    this.PendingDownloads.add(currFile);

                }

            }
    }
    
    public Exception LastError = null;

    public void setError(String message, Exception e) {
        LastError = e;
        setStatus(ImageStatus.Error);

        synchronized (listeners) {
            for (int x = 0; x < listeners.size(); x++) {
                listeners.get(x).Error(this, LastError);
            }
        }
    }

    /**
     * Manually call this method to write or check the initial ISO Image.
     */
    public void Initialize() {
        try {
            isowriter.WriteStartingISO(true);
            
            if (isowriter.Progress.isComplete())
            {
                this.setStatus(ImageStatus.Finished);
            }
            else
            {   
                isowriter.start();
            }
        } 
        catch (Exception e) 
        {
            setError("Error while initializing the iso",e);
        }
    }
    
    public void setParentLogger(Logger parent)
    {
        logger.setParent(parent);
        logger.setLevel(null);
        isowriter.setParentLogger(parent);
        jigdo.io.JigdoFileReader.setParentLogger(parent);
        jigdo.io.TemplateFileReader.setParentLogger(parent);
    }

    @Override
    public void Progress(DownloadEntry entry, long bytesDownloaded) {

        synchronized (listeners) {
            for (int x = 0; x < listeners.size(); x++) {
                listeners.get(x).Progress(entry, bytesDownloaded);
            }
        }
    }

    @Override
    public void StateChanged(DownloadEntry entry, DownloadStatus changedState) {

        if (changedState == DownloadStatus.Downloaded) {
            synchronized (ActiveDownloads) {
                ActiveDownloads.remove(entry);
            }

            synchronized (FinishedDownloads) {
                FinishedDownloads.add(entry);
            }

            isowriter.QueueWrite(entry);

            if (PendingDownloads.size() == 0 && ActiveDownloads.size() == 0
                    && FinishedDownloads.size() == jigdofile.Files.size()) {
                setStatus(ImageStatus.VerifyingFinished);

            }


        } else if (changedState == DownloadStatus.StartingDownload) {
            synchronized (PendingDownloads) {

                PendingDownloads.remove(entry);
            }

            synchronized (ActiveDownloads) {
                ActiveDownloads.add(entry);
            }
        } else if (changedState == DownloadStatus.Aborted
                || changedState == DownloadStatus.Error
                || changedState == DownloadStatus.Waiting
                || changedState == DownloadStatus.FailedMD5) {

            synchronized (ActiveDownloads) {
                ActiveDownloads.remove(entry);
            }

            synchronized (PendingDownloads) {
                PendingDownloads.add(entry);
            }

            entry.DownloadedBytes = null;
            entry.setDownloadStatus(DownloadStatus.NotStarted);
        }

        synchronized (listeners) {
            for (int x = 0; x < listeners.size(); x++) {
                listeners.get(x).StateChanged(entry, changedState);
            }
        }
    }

    @Override
    public void Error(DownloadEntry entry, Throwable error) {
        synchronized (listeners) {
            for (int x = 0; x < listeners.size(); x++) {
                listeners.get(x).Error(entry, error);
            }
        }
    }

    public void setStatus(ImageStatus status) {
        synchronized (listeners) {
            for (int x = 0; x < listeners.size(); x++) {
                listeners.get(x).StatusChange(this, status);
            }
        }
    }

    private java.util.ArrayList<ImageDownloadListener> listeners;

    public void AddListener(ImageDownloadListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }
}
